/**
  ******************************************************************************
  * @file    bsp_adc.c
  * @author  Iron
  * @date    2018-08-29
  * @version v1.0
  * @brief   bsp_adc c file
    *          TIM8 trigger
  */

/** @addtogroup GROUP_BSP
  * @{
  */

/* includes ------------------------------------------------------------------*/
#include "FreeRTOS.h"
#include "timers.h"
#include "stm32f4xx_hal.h"

/* private typedef -----------------------------------------------------------*/
/* private macro -------------------------------------------------------------*/
#define BSP_ADC_ENTER_ISR() \
  UBaseType_t uxSavedInterruptStatus = portSET_INTERRUPT_MASK_FROM_ISR()

#define BSP_ADC_EXIT_ISR() \
  portCLEAR_INTERRUPT_MASK_FROM_ISR(uxSavedInterruptStatus)

#define BSP_ADC1_DMA_IRQ_PRE_PRI  11        /* 5 < PRI < 15*/
#define BSP_ADC1_DMA_IRQ_SUB_PRI  0

/* ADC config */
#define BSP_ADC1_VREF             2500     // 2.5v
#define BSP_ADC1_VREFVALUE        (1<<12)  // 12bit

#define BSP_ADC_DEV_BAT_CHANNEL  ADC_CHANNEL_11
#define BSP_ADC_CAR_BAT_CHANNEL  ADC_CHANNEL_13
#define BSP_ADC_MCU_TEMP_CHANNEL ADC_CHANNEL_TEMPSENSOR  // ADC_CHANNEL_16
#define BSP_ADC_RTC_BAT_CHANNEL  ADC_CHANNEL_VBAT        // ADC_CHANNEL_18

/* RANK */
enum
{
    BSP_ADC_DEV_BAT_RANK = 1,
    BSP_ADC_CAR_BAT_RANK,
    BSP_ADC_MCU_TEMP_RANK,
    BSP_ADC_RTC_BAT_RANK,
    BSP_ADC_RANK_MAX,
};

#define BSP_ADC_RANK_NUM (BSP_ADC_RANK_MAX - 1)

/* private variables ---------------------------------------------------------*/
ADC_HandleTypeDef hadc1;
DMA_HandleTypeDef hdma_adc1;
__IO uint16_t bsp_adc_val[BSP_ADC_RANK_NUM];

static TimerHandle_t adc_tmrId;
static StaticTimer_t adc_tmr;

/* private function prototypes -----------------------------------------------*/
/* private functions ---------------------------------------------------------*/
extern void _Error_Handler(char *file, int line);

/**
* @brief This function handles DMA2 stream4 global interrupt.
*/
void DMA2_Stream4_IRQHandler(void)
{
    BSP_ADC_ENTER_ISR();
    HAL_DMA_IRQHandler(&hdma_adc1);
    BSP_ADC_EXIT_ISR();
}

/**
  * Enable DMA controller clock
  */
static void MX_DMA_Init(void)
{
    /* DMA controller clock enable */
    __HAL_RCC_DMA2_CLK_ENABLE();

    /* DMA interrupt init */
    /* DMA2_Stream4_IRQn interrupt configuration */
    HAL_NVIC_SetPriority(DMA2_Stream4_IRQn, BSP_ADC1_DMA_IRQ_PRE_PRI, BSP_ADC1_DMA_IRQ_SUB_PRI);
    HAL_NVIC_EnableIRQ(DMA2_Stream4_IRQn);
}

/* ADC1 init function */
static void MX_ADC1_Init(void)
{
    ADC_ChannelConfTypeDef sConfig;

    /**Configure the global features of the ADC (Clock, Resolution, Data Alignment and number of conversion)
    */
    hadc1.Instance = ADC1;
    hadc1.Init.ClockPrescaler = ADC_CLOCKPRESCALER_PCLK_DIV8;
    hadc1.Init.Resolution = ADC_RESOLUTION_12B;
    hadc1.Init.ScanConvMode = ENABLE;
    hadc1.Init.ContinuousConvMode = DISABLE;
    hadc1.Init.DiscontinuousConvMode = DISABLE;
    hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;
    hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
    hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
    hadc1.Init.NbrOfConversion = BSP_ADC_RANK_NUM;
    hadc1.Init.DMAContinuousRequests = ENABLE;
    hadc1.Init.EOCSelection = ADC_EOC_SEQ_CONV;
    if (HAL_ADC_Init(&hadc1) != HAL_OK)
    {
        _Error_Handler(__FILE__, __LINE__);
    }

    /**Configure for the selected ADC regular channel its corresponding rank in the sequencer and its sample time.
    */
    sConfig.Channel = BSP_ADC_DEV_BAT_CHANNEL;
    sConfig.Rank = BSP_ADC_DEV_BAT_RANK;
    sConfig.SamplingTime = ADC_SAMPLETIME_112CYCLES;
    if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
    {
        _Error_Handler(__FILE__, __LINE__);
    }

    /**Configure for the selected ADC regular channel its corresponding rank in the sequencer and its sample time.
    */
    sConfig.Channel = BSP_ADC_CAR_BAT_CHANNEL;
    sConfig.Rank = BSP_ADC_CAR_BAT_RANK;
    if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
    {
        _Error_Handler(__FILE__, __LINE__);
    }

    /**Configure for the selected ADC regular channel its corresponding rank in the sequencer and its sample time.
    */
    sConfig.Channel = BSP_ADC_MCU_TEMP_CHANNEL;
    sConfig.Rank = BSP_ADC_MCU_TEMP_RANK;
    if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
    {
        _Error_Handler(__FILE__, __LINE__);
    }

    /**Configure for the selected ADC regular channel its corresponding rank in the sequencer and its sample time.
    */
    sConfig.Channel = BSP_ADC_RTC_BAT_CHANNEL;
    sConfig.Rank = BSP_ADC_RTC_BAT_RANK;
    if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
    {
        _Error_Handler(__FILE__, __LINE__);
    }
}

void HAL_ADC_MspInit(ADC_HandleTypeDef *hadc)
{
    GPIO_InitTypeDef GPIO_InitStruct;

    if (hadc->Instance == ADC1)
    {
        /* USER CODE BEGIN ADC1_MspInit 0 */

        /* USER CODE END ADC1_MspInit 0 */
        /* Peripheral clock enable */
        __HAL_RCC_ADC1_CLK_ENABLE();

        /**ADC1 GPIO Configuration
        PC1     ------> ADC1_IN11
        PC3     ------> ADC1_IN13
        */
        GPIO_InitStruct.Pin = GPIO_PIN_1 | GPIO_PIN_3;
        GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
        GPIO_InitStruct.Pull = GPIO_NOPULL;
        HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);

        /* ADC1 DMA Init */
        /* ADC1 Init */
        hdma_adc1.Instance = DMA2_Stream4;
        hdma_adc1.Init.Channel = DMA_CHANNEL_0;
        hdma_adc1.Init.Direction = DMA_PERIPH_TO_MEMORY;
        hdma_adc1.Init.PeriphInc = DMA_PINC_DISABLE;
        hdma_adc1.Init.MemInc = DMA_MINC_ENABLE;
        hdma_adc1.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;
        hdma_adc1.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;
        hdma_adc1.Init.Mode = DMA_NORMAL;
        hdma_adc1.Init.Priority = DMA_PRIORITY_LOW;
        hdma_adc1.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
        if (HAL_DMA_Init(&hdma_adc1) != HAL_OK)
        {
            _Error_Handler(__FILE__, __LINE__);
        }

        __HAL_LINKDMA(hadc, DMA_Handle, hdma_adc1);

        /* USER CODE BEGIN ADC1_MspInit 1 */

        /* USER CODE END ADC1_MspInit 1 */
    }
}

void HAL_ADC_MspDeInit(ADC_HandleTypeDef *hadc)
{
    if (hadc->Instance == ADC1)
    {
        /* USER CODE BEGIN ADC1_MspDeInit 0 */

        /* USER CODE END ADC1_MspDeInit 0 */
        /* Peripheral clock disable */
        __HAL_RCC_ADC1_CLK_DISABLE();

        /**ADC1 GPIO Configuration
        PC1     ------> ADC1_IN11
        PC3     ------> ADC1_IN13
        */
        HAL_GPIO_DeInit(GPIOC, GPIO_PIN_1 | GPIO_PIN_3);

        /* ADC1 DMA DeInit */
        HAL_DMA_DeInit(hadc->DMA_Handle);
        /* USER CODE BEGIN ADC1_MspDeInit 1 */

        /* USER CODE END ADC1_MspDeInit 1 */
    }
}

/**
  * @brief  Conversion complete callback in non blocking mode
  * @param  AdcHandle : AdcHandle handle
  * @note   This example shows a simple way to report end of conversion, and
  *         you can add your own implementation.
  * @retval None
  */
void HAL_ADC_ErrorCallback(ADC_HandleTypeDef *hadc)
{
    /* Set ADC error code to none */
    ADC_CLEAR_ERRORCODE(hadc);
    HAL_ADC_Stop_DMA(&hadc1);
}

static void adc_timer_cb(TimerHandle_t xExpiredTimer)
{
    HAL_ADC_Stop_DMA(&hadc1);
    HAL_ADC_Start_DMA(&hadc1, (uint32_t *)bsp_adc_val, BSP_ADC_RANK_NUM);
}

int32_t bsp_adc_init(void)
{
    MX_DMA_Init();
    MX_ADC1_Init();

    adc_tmrId = xTimerCreateStatic("adc", pdMS_TO_TICKS(300), pdTRUE, NULL, adc_timer_cb, &adc_tmr);
    xTimerStart(adc_tmrId, 0);

    return 0;
}

uint32_t bsp_adc_get_power_dev_bat_voltage(void)
{
    uint32_t dev_bat_adc_val = bsp_adc_val[ BSP_ADC_DEV_BAT_RANK - 1 ]; // 0xFFF
    return dev_bat_adc_val * 2 * BSP_ADC1_VREF / BSP_ADC1_VREFVALUE; // mV
}

uint32_t bsp_adc_get_power_car_bat_voltage(void)
{
    uint32_t car_bat_adc_val = bsp_adc_val[ BSP_ADC_CAR_BAT_RANK - 1 ]; // 0xFFF
    return car_bat_adc_val * 13 * BSP_ADC1_VREF / BSP_ADC1_VREFVALUE; // mV  37: 0.3v OFFSET, 电源输入肖特基二极管
}

uint32_t bsp_adc_get_power_rtc_bat_voltage(void)
{
    uint32_t rtc_bat_adc_val = bsp_adc_val[ BSP_ADC_RTC_BAT_RANK - 1 ]; // 0xFFF
    return rtc_bat_adc_val * 2 * BSP_ADC1_VREF / BSP_ADC1_VREFVALUE ; // mV
}

/**
  * Avg_Slope 2.5mv/℃
  * V25 0.76V
  */
double bsp_adc_get_mcu_temperature(void)
{
    uint32_t mcu_temp_adc_val = bsp_adc_val[ BSP_ADC_MCU_TEMP_RANK - 1 ]; // 0xFFF
    double mcu_temp_voltage = mcu_temp_adc_val * BSP_ADC1_VREF / BSP_ADC1_VREFVALUE - 760;
    mcu_temp_voltage = mcu_temp_voltage / 2.5 + 25;
    return mcu_temp_voltage; // ℃
}

/**
  * @}
  */

/******************* (C)COPYRIGHT 2018 ***** END OF FILE *********************/
